package com.wxd.filter;

import com.wxd.bean.MethodInvokeInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
// 在消费端记录每个方法的耗时情况
@Activate(group = CommonConstants.CONSUMER)
public class TPMonitorFilter implements Filter, Runnable {
    // 过去一分钟内的方法调用情况
    private static final Map<String, List<MethodInvokeInfo>> methodInvokeInfoMap = new ConcurrentHashMap<>();

    public TPMonitorFilter() {
        // 5秒后进行第一次统计，此后每隔5秒统计一次
        Executors.newScheduledThreadPool(1).scheduleWithFixedDelay(this, 5, 5, TimeUnit.SECONDS);
    }

    // 方法调用时，保存调用信息
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long cost;
        Result result = null;
        try {
            Long startTime = System.currentTimeMillis();
            result = invoker.invoke(invocation);
            if (result.getException() instanceof Exception) {
                throw new Exception(result.getException());
            }
            cost = System.currentTimeMillis() - startTime;
        } catch (Exception e) {
            e.printStackTrace();
            return result;
        }
        String methodName = invocation.getMethodName();
        // 按方法名称找到指定方法的调用情况集合
        List<MethodInvokeInfo> methodMonitorList = methodInvokeInfoMap.get(methodName);
        // 每个方法第一次调用时，对应集合是空，创建一个list放入map
        if (methodMonitorList == null) {
            methodMonitorList = new ArrayList<>();
            methodInvokeInfoMap.put(methodName, methodMonitorList);
        }
        methodMonitorList.add(new MethodInvokeInfo(methodName, cost, System.currentTimeMillis()));
        return result;
    }


    // 每5秒钟统计一次，最近一分钟内的每个方法的TopN调用情况并打印，
    // 为了减少重复处理，统计过程中还会移除超过一分钟的调用信息
    @Override
    public void run() {
        System.out.println("====================执行topN统计=====================");
        for (Map.Entry<String, List<MethodInvokeInfo>> entry : methodInvokeInfoMap.entrySet()) {
            List<MethodInvokeInfo> invokeInfos = entry.getValue();
            log.info("方法{}的top90执行耗时：{}毫秒", entry.getKey(), getMethodTopN(invokeInfos, 0.9));
            log.info("方法{}的top99执行耗时：{}毫秒", entry.getKey(), getMethodTopN(invokeInfos, 0.99));
        }
    }

    /**
     * 对方法一分钟内的响应时间进行排序，并返回topN的时间
     * @param invokeInfos
     * @param rate
     * @return
     */
    private long getMethodTopN(List<MethodInvokeInfo> invokeInfos, double rate) {
        // 创建临时数组，保存每分钟的数据
        List<MethodInvokeInfo> lastOneMinuteInvokeInfos = new ArrayList<>(60);

        long currentTime = System.currentTimeMillis();
        // 时间边界值，如果方法的调用时间在边界时间以外，则为无效，需要移除。
        long boundary = currentTime - 60000;
        // 注意：由于遍历的同时需要删除过期的记录，所以必须通过迭代器来遍历
        Iterator<MethodInvokeInfo> iterator = invokeInfos.iterator();
        while (iterator.hasNext()) {
            MethodInvokeInfo info = iterator.next();
            // 清理过期数据：方法的调用时间已经超过一分钟，需要从map中移除
            if (info.getEndTime() < boundary) {
                iterator.remove();
            } else {// 1分钟内的有效调用记录，需要进行统计
                lastOneMinuteInvokeInfos.add(info);
            }
        }
        // 按执行耗时排序
        Collections.sort(lastOneMinuteInvokeInfos);
        System.out.println(lastOneMinuteInvokeInfos);
        // 按百分比找到对应的索引
        int index = (int) (lastOneMinuteInvokeInfos.size() * rate);
        return lastOneMinuteInvokeInfos.get(index).getCost();
    }
}
